﻿using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;

namespace JsonToXml
{
    /// <summary>
    /// Convert a Json String into a Xml String
    /// Version 1.2 by florian bruggisser
    /// </summary>
    class JsonToXmlParser
    {
        /// <summary>
        /// Convert a json string to a Xml String
        /// </summary>
        /// <param name="jsondata">Json Data String</param>
        /// <returns>Xml String Object</returns>
        public string ConvertToXml(string jsondata)
        {
            string xmlfromjson = WriteXmlFromJson(ParseJson(jsondata));
            return xmlfromjson;
        }

        /// <summary>
        /// Parse Json into Elements
        /// </summary>
        /// <param name="jsondata">Json Data String</param>
        /// <returns>Sorted MatchCollection of Elements</returns>
        private Match[] ParseJson(string jsondata)
        {
            List<Match> matches = new List<Match>();

            //startpatt: "(?<element>[\w]*)":(?<etype>[\[\{"])(?<content>[[:alnum:][:space:]:.\\/-]*)
            //endpatt: (?<endelement>[\}\]"])[:,\}] | 2.0: (?<endelement>[\}\]])
            string startpatterns = "\"(?<ename>[\\w]*)\":(?<etype>[\\[\\{\"])(?<content>[\\w\\s:.\\\\/-]*)";
            string endpatterns = "(?<etype>[\\}\\]])";

 
            //Add patterns to matchlist
            AddMatchCollectionToList(matches, Regex.Matches(jsondata, startpatterns));
            AddMatchCollectionToList(matches, Regex.Matches(jsondata, endpatterns));

            //sort list
            matches.Sort(new MatchSorter());

            return matches.ToArray();
        }

        /// <summary>
        /// Fills a Matchcollection into a matchlist (Linq workaround)
        /// </summary>
        /// <param name="matchlist">List with matches</param>
        /// <param name="matchcol">Collection with matches</param>
        private void AddMatchCollectionToList(List<Match> matchlist, MatchCollection matchcol)
        {
            foreach (Match m in matchcol)
            {
                matchlist.Add(m);
            }
        }

        /// <summary>
        /// Writes Matches into Xml Data String
        /// </summary>
        /// <param name="matches">Element Matches</param>
        /// <returns>Xml Data String</returns>
        private string WriteXmlFromJson(Match[] matches)
        {
            StringWriter xmlstring = new StringWriter();
            XmlTextWriter xmlwriter = new XmlTextWriter(xmlstring);

            int OpenNodes = 0;
            int MatchCounter = 0;

            xmlwriter.WriteStartDocument(true);
            xmlwriter.WriteRaw(xmlstring.NewLine);

            //iterate trough matches
            foreach (Match m in matches)
            {
                //catch element values
                string elementtype = m.Groups["etype"].Value;
                string ename = m.Groups["ename"].Value;
                string content = m.Groups["content"].Value;

                MatchCounter++;

                //switch element types and do stuff for them
                switch (elementtype)
                {
                    case "[":
                        xmlwriter.WriteStartElement(ename);
                        OpenNodes++;
                        break;
                    case "{":
                        xmlwriter.WriteStartElement(ename);
                        OpenNodes++;
                        break;
                    case "\"":
                        xmlwriter.WriteElementString(ename, content);
                        break;
                    case "]":
                        //check if it is root node which will be closed
                        if (OpenNodes - 1 > 1)
                        {
                            xmlwriter.WriteEndElement();
                            OpenNodes--;
                        }
                        else
                        {
                            if (MatchCounter < matches.Length)
                            {
                                xmlwriter.WriteEndElement();
                                OpenNodes--;
                            }
                        }
                        break;
                    case "}":
                        //check if it is root node which will be closed
                        if (OpenNodes - 1 > 1)
                        {
                            xmlwriter.WriteEndElement();
                            OpenNodes--;
                        }
                        else
                        {
                            if (MatchCounter == matches.Length)
                            {
                                xmlwriter.WriteEndElement();
                                OpenNodes--;
                            }
                        }
                        break;
                }

                xmlwriter.WriteRaw(xmlstring.NewLine);
            }

            //close and return
            xmlstring.Close();
            return xmlstring.ToString().Replace("\\", "");
        }

        /// <summary>
        /// Sorts Match Data by it's index
        /// </summary>
        private class MatchSorter : IComparer<Match>
        {
            public int Compare(Match obj1, Match obj2)
            {
                return obj1.Index.CompareTo(obj2.Index);
            }
        }
    }
}
